﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjectionTest.Bindings;
using Should;

namespace MugenInjectionTest.Converters
{
    [TestClass]
    public class AdapterDynamicConverterTest : TestBase
    {
        [TestMethod]
        public void BindAsAdapterForAllTest()
        {
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value);
                injector.BindAsAdapterForAll<ISimple, IAdapter>((context, @class) =>
                                                                       {
                                                                           @class.ShouldEqual(value);
                                                                           return new Adapter(value2);
                                                                       });

                var adapter = injector.Get<IAdapter>();
                adapter.Simple.ShouldEqual(value2);
            }
        }

        [TestMethod]
        public void BindAsAdapterForAllDuplicateShouldThrowErrorTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.BindAsAdapterForAll<ISimple, IAdapter>((context, @class) => new Adapter(@class));
                injector.BindAsAdapterForAll<object, IAdapter>((context, @class) => new Adapter(null));
                TestUtils.Catch<ConflictBindingException>(() => injector.Get<IAdapter>());
            }
        }

        [TestMethod]
        public void UnbindAllAdaptersForTest()
        {
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value);
                injector.BindAsAdapterForAll<ISimple, IAdapter>((context, @class) =>
                {
                    @class.ShouldEqual(value);
                    return new Adapter(value2);
                });

                var adapter = injector.Get<IAdapter>();
                adapter.Simple.ShouldEqual(value2);

                injector.UnbindAllAdaptersFor<ISimple, IAdapter>().ShouldBeTrue();
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());
            }
        }

        [TestMethod]
        public void UnbindAllAdaptersForWithConditionTest()
        {
            const string name1 = "name1";
            const string name2 = "name2";
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            var value3 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value).NamedBinding(name1);
                injector.Bind<ISimple>().ToConstant(value).NamedBinding(name2);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.Named(name1), (context, @class) => new Adapter(value2));
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.Named(name2), (context, @class) => new Adapter(value3));

                var adapter = injector.Get<IAdapter>(name1);
                adapter.Simple.ShouldEqual(value2);

                adapter = injector.Get<IAdapter>(name2);
                adapter.Simple.ShouldEqual(value3);

                injector.UnbindAllAdaptersFor<ISimple, IAdapter>().ShouldBeTrue();
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>(name1));
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>(name2));
            }
        }

        [TestMethod]
        public void BindAsAdapterForNamedTest()
        {
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value).NamedBinding(name);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.Named(name),
                                                             (context, @class) =>
                                                                 {
                                                                     @class.ShouldEqual(value);
                                                                     return new Adapter(value2);
                                                                 });

                var adapter = injector.Get<IAdapter>(name);
                adapter.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());

                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindAdaptersFor<ISimple, IAdapter>(null, null, dictionary, InjectorUtilsInternal.EmptyInjectionParams).ShouldBeTrue();
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>(name));
            }
        }

        [TestMethod]
        public void BindAsAdapterForWhenIntoTest()
        {
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();

            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value).WhenInto(TypeInto.AsType());
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenInto(TypeInto.AsType()),
                                                             (context, @class) =>
                                                                 {
                                                                     @class.ShouldEqual(value);
                                                                     return new Adapter(value2);
                                                                 });

                var adapter = injector.GetInto<IAdapter>(TypeInto);
                adapter.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());

                //Named
                injector.Bind<ISimple>().ToConstant(value2).WhenInto(TypeInto.AsType()).NamedBinding(name);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenInto(TypeInto.AsType()).Named(name),
                                                             (context, @class) =>
                                                                 {
                                                                     @class.ShouldEqual(value2);
                                                                     return new Adapter(value);
                                                                 });

                adapter = injector.GetInto<IAdapter>(name, TypeInto);
                adapter.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());


                //Unbind
                injector.UnbindAdaptersFor<ISimple, IAdapter>(TypeInto, null,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(TypeInto));

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindAdaptersFor<ISimple, IAdapter>(TypeInto, null,
                                                              dictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(name, TypeInto));
            }
        }

        [TestMethod]
        public void BindAsAdapterForWhenIntoIsAssignableTest()
        {
            var typeInto = typeof(MemberInfo).GetTypeInfo();
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();


            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value).WhenIntoIsAssignable(typeInto.AsType());
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenIntoIsAssignable(typeInto.AsType()),
                                                             (context, @class) =>
                                                             {
                                                                 @class.ShouldEqual(value);
                                                                 return new Adapter(value2);
                                                             });

                var adapter = injector.GetInto<IAdapter>(typeof(PropertyInfo).GetTypeInfo());
                adapter.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => adapter = injector.GetInto<IAdapter>(typeof(object).GetTypeInfo()));

                //Named
                injector.Bind<ISimple>().ToConstant(value2).WhenIntoIsAssignable(typeInto.AsType()).NamedBinding(name);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenIntoIsAssignable(typeInto.AsType()).Named(name),
                                                             (context, @class) =>
                                                             {
                                                                 @class.ShouldEqual(value2);
                                                                 return new Adapter(value);
                                                             });

                adapter = injector.GetInto<IAdapter>(name, typeof(MethodInfo).GetTypeInfo());
                adapter.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());

                //Unbind
                injector.UnbindAdaptersFor<ISimple, IAdapter>(typeInto, null,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(typeof(PropertyInfo).GetTypeInfo()));

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindAdaptersFor<ISimple, IAdapter>(typeInto, null,
                                                              dictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(name, typeof(MethodInfo).GetTypeInfo()));
            }
        }

        [TestMethod]
        public void BindAsAdapterForWhenClassHasAttributeTest()
        {
            var attribute = typeof(SimpleAttribute);
            var typeInto = typeof(Adapter).GetTypeInfo();
            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();


            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value).WhenClassHasAttribute(attribute);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenClassHasAttribute(attribute),
                                                             (context, @class) =>
                                                             {
                                                                 @class.ShouldEqual(value);
                                                                 return new Adapter(value2);
                                                             });

                var adapter = injector.GetInto<IAdapter>(typeInto);
                adapter.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => adapter = injector.GetInto<IAdapter>(typeof(object).GetTypeInfo()));

                //Named
                injector.Bind<ISimple>().ToConstant(value2).WhenClassHasAttribute(attribute).NamedBinding(name);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenClassHasAttribute(attribute).Named(name),
                                                             (context, @class) =>
                                                             {
                                                                 @class.ShouldEqual(value2);
                                                                 return new Adapter(value);
                                                             });

                adapter = injector.GetInto<IAdapter>(name, typeInto);
                adapter.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());

                //Unbind
                injector.UnbindAdaptersFor<ISimple, IAdapter>(typeInto, null,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(typeInto));

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindAdaptersFor<ISimple, IAdapter>(typeInto, null,
                                                              dictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(name, typeInto));
            }
        }

        [TestMethod]
        public void BindAsAdapterForWhenNamespaceEqualTest()
        {
            var typeInto = typeof(Adapter).GetTypeInfo();
            var ns = typeInto.Namespace;

            const string name = "name";
            var value = new SimpleClass();
            var value2 = new SimpleClass();


            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<ISimple>().ToConstant(value).WhenNamespaceEqual(ns);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenNamespaceEqual(ns),
                                                             (context, @class) =>
                                                             {
                                                                 @class.ShouldEqual(value);
                                                                 return new Adapter(value2);
                                                             });

                var adapter = injector.GetInto<IAdapter>(typeInto);
                adapter.Simple.ShouldEqual(value2);
                TestUtils.Catch<BindingNotFoundException>(() => adapter = injector.GetInto<IAdapter>(typeof(object).GetTypeInfo()));

                //Named
                injector.Bind<ISimple>().ToConstant(value2).WhenNamespaceEqual(ns).NamedBinding(name);
                injector.BindAsAdapterFor<ISimple, IAdapter>(builder => builder.WhenNamespaceEqual(ns).Named(name),
                                                             (context, @class) =>
                                                             {
                                                                 @class.ShouldEqual(value2);
                                                                 return new Adapter(value);
                                                             });

                adapter = injector.GetInto<IAdapter>(name, typeInto);
                adapter.Simple.ShouldEqual(value);
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get<IAdapter>());

                //Unbind
                injector.UnbindAdaptersFor<ISimple, IAdapter>(typeInto, null,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(typeInto));

                //Unbind named
                var dictionary = new Dictionary<string, object>();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, name);
                injector.UnbindAdaptersFor<ISimple, IAdapter>(typeInto, null,
                                                              dictionary,
                                                              InjectorUtilsInternal.EmptyInjectionParams);
                TestUtils.Catch<BindingNotFoundException>(() => injector.GetInto<IAdapter>(name, typeInto));
            }
        }

        #region Nested type:Adapter

        public interface IAdapter
        {
            ISimple Simple { get; }
        }

        [Simple]
        public class Adapter : IAdapter
        {
            /// <summary>
            ///     Initializes a new instance of the <see cref="T:System.Object" /> class.
            /// </summary>
            public Adapter(ISimple simpleClass)
            {
                Simple = simpleClass;
            }

            public ISimple Simple { get; set; }
        }

        #endregion

        #region Nested type:SimpleClass

        public interface ISimple
        {

        }

        public class SimpleClass : ISimple
        {
        }

        #endregion
    }
}